#include "biped_anim.h"

#include "script/scripthelper.h"
#include "script/file_list.h"

#include "lang/lang.h"
#include "dd/dd_man.h"
#include "font/font_manager.h"
#include "math/facing.h"
#include "math/math_core.h"
#include "graphics/graphics_util.h"
#include "vfs/vfs.h"
#include "ui/ui_manager.h"

#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <cassert>
#include <algorithm>
#include <math.h>

using namespace bones;
using namespace script;



BipedAnimationSet&	BipedAnimationSet::Instance()
{
	static BipedAnimationSet	Inst;
	return Inst;
}

BipedAnimationSet::BipedAnimationSet()
{
	AddAll();
}

BipedAnimationSet::~BipedAnimationSet()
{

}

void	BipedAnimationSet::RefreshAll()
{
	m_Animations.ClearSet();
	AddAll();
}


void	BipedAnimationSet::ConvertAll()
{
	// only convert from LUA
	script::FileList		List;
	List.Build("anim/*.sbp");
	List.Build("bganim/*.sbp");

	for (int i=0; i<List.GetFileC(); i++)		AddAnimationLua(List.GetFileName(i));

	SaveAll();
}

void	BipedAnimationSet::AddAll()
{
	// read all animations from /anim/*.bip
	// we need an alternative path for archives.. load from LUA set instead?
	std::vector<std::string> FileList;
	int FileC;

	FileC = vfs::VFS::Instance().BuildFileList(FileList, "anim", "bpp");
	FileC += vfs::VFS::Instance().BuildFileList(FileList, "bganim", "bpp");
	if (FileC > 0)
	{
		for (int i=0; i<(int)FileList.size(); i++)	AddAnimationBin(FileList[i]);
	}
	else
	{
		script::FileList		List;
		List.Build("anim/*.bpp");
		List.Build("bganim/*.bpp");
		for (int i=0; i<List.GetFileC(); i++)		AddAnimationBin(List.GetFileName(i));
	}

#if defined(_PLATFORM_WII) || defined(_PLATFORMED_IOS) || defined(_PLATFORMED_ANDROID) 

	FileC = vfs::VFS::Instance().BuildFileList(FileList, "anim", "sbp");
	FileC += vfs::VFS::Instance().BuildFileList(FileList, "bganim", "sbp");
	if (FileC > 0)
	{
		for (int i=0; i<(int)FileList.size(); i++)	AddAnimationLua(FileList[i]);
	}
	else
	{
		//List.Reset();	// reset and add the lua files
		script::FileList		List;
		List.Build("anim/*.sbp");
		List.Build("bganim/*.sbp");

		for (int i=0; i<List.GetFileC(); i++)		AddAnimationLua(List.GetFileName(i));
	}

	// output them for future reference
	SaveAll();
#endif

}

void	BipedAnimationSet::AddAnimationLua(const std::string& Name)
{
	// provide a load-path for binary later
	std::string	BaseName = vfs::VFS::GetBaseName(Name);
	if (GetAnimation(BaseName)) 
	{
		//_LOG(MSG_ERROR, "Animation " << Name << " already exists!");
		return;
	}
	LuaPlus::LuaState*	pScript = ScriptHelper::CreateLuaState();
	if (!ScriptHelper::DoFile(*pScript, Name))
	{
		_LOG(MSG_ERROR, "Unable to load biped data " << Name << "!");
		 LuaPlus::LuaState::Destroy( pScript );
		return;
	} else
	{
		LuaPlus::LuaObject Info  = pScript->GetGlobal("AnimationData");

		BipedAnimation* pAnim = new BipedAnimation();
		memset(pAnim, 0, sizeof(BipedAnimation));
		BipedBone::SetString(pAnim->Name, BaseName);
		BipedBone::SetString(pAnim->BasePath, Name);
		pAnim->RunTime = 0;
		pAnim->Frames = 0;
		pAnim->Max = -math::Vec2(10000,10000);
		pAnim->Min = math::Vec2(10000, 10000);
		pAnim->BoneC = Info.GetTableCount();
		pAnim->BoneA = new BipedBone[pAnim->BoneC];
		memset(pAnim->BoneA, 0, sizeof(BipedBone)*pAnim->BoneC);
		for (int i=0; i<pAnim->BoneC; i++)
		{
			LuaPlus::LuaObject NewBone  = Info.GetByIndex(i+1);
			if (NewBone.IsTable())
			{
				BipedBone* pBone = &pAnim->BoneA[i];
				std::string		Name = ScriptHelper::GetTableString(NewBone, "Name");
				BipedBone::SetString(pBone->Name, Name);
				pBone->ID = num::CRC32::FastHash(Name);

				pBone->Length	= ScriptHelper::GetTableFloat(NewBone, "Height", 0) * GraphicsUtil::InvH;

				// default information
				BipedBone::SetString(pBone->GameData, ScriptHelper::GetTableString(NewBone, "GameData"));
				BipedBone::SetString(pBone->DefaultImg, ScriptHelper::GetTableString(NewBone, "DefaultImg"));
				pBone->DefaultSortL = ScriptHelper::GetTableFloat(NewBone, "DefaultsortL", 0);
				pBone->DefaultSortR	= ScriptHelper::GetTableFloat(NewBone, "DefaultsortR", pBone->DefaultSortL);

				LuaPlus::LuaObject	Frames = NewBone.GetByName("Frames");
				if (Frames.IsTable())
				{
					pBone->FrameC = Frames.GetTableCount();
					pBone->FrameA = new BipedAnimFrame[pBone->FrameC];
					memset(pBone->FrameA, 0, sizeof(BipedAnimFrame) * pBone->FrameC);
					for (int j=0; j<pBone->FrameC; j++)
					{
						LuaPlus::LuaObject		NewFrame = Frames[j+1];
						if (!NewFrame.IsTable()) continue;
						BipedAnimFrame&		FrameInfo = pBone->FrameA[j];

						FrameInfo.Pos	= ScriptHelper::GetTableVec2(NewFrame, "Pos");
						FrameInfo.Rot	= ScriptHelper::GetTableFloat(NewFrame, "Rot");
						FrameInfo.Flags	 = 0x00;
						if (ScriptHelper::GetTableBool(NewFrame, "Visible", true))
						{
							FrameInfo.SetFlag(BipedAnimFrame::FLAG_VISIBLE);	// else assume hidden
						}
				
						// character is 100 units high. We want it to be 100 pixels high (100/INVH)
						FrameInfo.Pos.x = (FrameInfo.Pos.x * GraphicsUtil::InvW) * GraphicsUtil::H2W;
						FrameInfo.Pos.y = (FrameInfo.Pos.y * GraphicsUtil::InvH);
					
						// update min/max
						pAnim->Max.x = UTIL_HIGH(pAnim->Max.x, FrameInfo.Pos.x);
						pAnim->Max.y = UTIL_HIGH(pAnim->Max.y, FrameInfo.Pos.y);

						pAnim->Min.x = UTIL_LOW(pAnim->Min.x, FrameInfo.Pos.x);
						pAnim->Min.y = UTIL_LOW(pAnim->Min.y, FrameInfo.Pos.y);
					}
					float		Time = pBone->FrameC * (1/30.0f);
					pAnim->RunTime = UTIL_HIGH(pAnim->RunTime, Time);
					pAnim->Frames = UTIL_HIGH(pAnim->Frames, (int)pBone->FrameC);
				}	
				if (pBone->FrameC <=0)
				{
					_LOG(MSG_WARNING, "Bone " << pBone->Name << " in animation " << pAnim->Name << " has no animations!");
				} 
			}
		}
		// okay .. we're done. add it
		UIMAN().UpdateLoadCB();
		m_Animations.AddElement(pAnim->Name, pAnim);
	}
	LuaPlus::LuaState::Destroy( pScript );
}


/////////////////////////////////////////
// Binary file format:
// BipedBinaryHeader
// followed by BoneC x ( BipedBinaryBone + (FrameC x BipedAnimFrame))
// followed by the FrameC x BoneC frames  ordered so that all frames for one bone's given before loading the next
struct	BipedBinaryHeader
{
	unsigned int		Magic;
	unsigned int		VersionID;
	FAKE_FP				RunTime;
	FAKE_FP				MinX;
	FAKE_FP				MinY;
	FAKE_FP				MaxX;
	FAKE_FP				MaxY;
	int					FrameC;
	int					BoneC;

// helper functions
	static unsigned int	GetMagic()  { return('bipb'); };
	static unsigned int	GetVersion() 
	{
		// v01: First version
		// v02: Changed from float->32bit multiplied ints to make it little/big endian portable
		// v03: Added visibility flag in the bone
		// v04: Reduced max bone-length to align with new bone format
		return 0x04;
	}
};

struct BipedBinaryBone
{
	static const int			BONE_NAME_MAX = BipedBone::NAME_LENGTH;
	char						Name[BONE_NAME_MAX];
	FAKE_FP						Length;
	char						DefaultImg[BONE_NAME_MAX];
	FAKE_FP						DefaultSortL;
	FAKE_FP						DefaultSortR;
};

struct				BipedBinaryFrame
{
	FAKE_FP			PosX;
	FAKE_FP			PosY;
	FAKE_FP			Rot;
	int				Flags;
};


void	BipedAnimationSet::AddAnimationBin(const std::string& Name)
{
	// provide a load-path for binary later

//	_LOG(MSG_COMMENT, "Loading Animation file " << Name);
	std::string	BaseName = vfs::VFS::GetBaseName(Name);
	if (GetAnimation(BaseName)) 
	{
		_LOG(MSG_ERROR, "+Animation " << Name << " already exists!");
		return;
	}
	vfs::LoadedBlock	Block;
	if (!vfs::VFS::Instance().GetFile(Name, Block))
	{
		_LOG(MSG_ERROR, "+Unable to load Animation " << Name << "!");
		return;
	}
	BipedBinaryHeader	Header;
	Block.ReadData(&Header, sizeof(Header));
	if (cpu_to_le32(Header.Magic)!=Header.GetMagic() ||
		 cpu_to_le32(Header.VersionID) !=Header.GetVersion())
	{
		_LOG(MSG_ERROR, "+Animation file has invalid magic or version ID");
		return;
	}
	
	BipedAnimation*	pAnim = new BipedAnimation;
	memset(pAnim, 0, sizeof(BipedAnimation));
	pAnim->Frames = cpu_to_le32(Header.FrameC);
	BipedBone::SetString(pAnim->Name, BaseName);
	pAnim->Min = math::Vec2(MathFunc::ToFP(Header.MinX), MathFunc::ToFP(Header.MinY));
	pAnim->Max = math::Vec2(MathFunc::ToFP(Header.MaxX), MathFunc::ToFP(Header.MaxY));
	pAnim->RunTime = MathFunc::ToFP(Header.RunTime);
	BipedBone::SetString(pAnim->BasePath, Name);

	// load all the bones
	pAnim->BoneC = cpu_to_le32(Header.BoneC);
	pAnim->BoneA = new BipedBone[pAnim->BoneC];
	memset(pAnim->BoneA, 0, sizeof(BipedBone)*pAnim->BoneC);

	for (int i=0; i<pAnim->BoneC; i++)
	{
		// get the data
		BipedBinaryBone	Bone;
		Block.ReadData(&Bone, sizeof(Bone));

		// copy over to an actual internal data-structure
		// would it be worthwhile to use the binary as the internal representation?
		BipedBone*	pBone = &pAnim->BoneA[i];
		strcpy(pBone->Name, Bone.Name);
		pBone->ID = num::CRC32::FastHash(Bone.Name);

		strcpy(pBone->DefaultImg, Bone.DefaultImg);
		pBone->DefaultSortL = MathFunc::ToFP(Bone.DefaultSortL);
		pBone->DefaultSortR = MathFunc::ToFP(Bone.DefaultSortR);
		pBone->Length = MathFunc::ToFP(Bone.Length);

		pBone->FrameC = pAnim->Frames;
		pBone->FrameA = new BipedAnimFrame[pBone->FrameC];
		// would it be better to store and operate on this as fixed point, and merge the two formats instead?
		for (int i=0; i<pBone->FrameC; i++)
		{
			static BipedBinaryFrame	FrameInfo;
			Block.ReadData(&FrameInfo, sizeof(FrameInfo));
			pBone->FrameA[i].Pos.x = MathFunc::ToFP(FrameInfo.PosX);
			pBone->FrameA[i].Pos.y = MathFunc::ToFP(FrameInfo.PosY);
			pBone->FrameA[i].Rot = MathFunc::ToFP(FrameInfo.Rot);
			pBone->FrameA[i].Flags = cpu_to_le32(FrameInfo.Flags);
		}

	}
	if (!ui::UISprite::NO_LOAD_IMAGE)	// only update load-bar if we're interested in that sort of thing
	{
		UIMAN().UpdateLoadCB();
	}
	m_Animations.AddElement(pAnim->Name, pAnim);
}

void	BipedAnimationSet::SaveAnimationBin(BipedAnimation*	pAnim)
{
	assert(pAnim);
	// save it out to basepath but with the extension rewritten
	std::string	File = vfs::VFS::StripExtension(pAnim->BasePath);
	File+=".bpp";
	FILE*	pFile = fopen(File.c_str(), "wb+");
	if (!pFile)
	{
		_LOG(MSG_ERROR, "Unable to open file " << File << " for exporting animation "<< pAnim->Name);
		return;
	}
	// save out a binary file
	BipedBinaryHeader	Header;
	Header.Magic = Header.GetMagic();
	Header.VersionID = Header.GetVersion();
	Header.BoneC = pAnim->BoneC;
	Header.FrameC = pAnim->Frames;
	Header.MinX = MathFunc::FromFP(pAnim->Min.x);
	Header.MinY = MathFunc::FromFP(pAnim->Min.y);
	Header.MaxX = MathFunc::FromFP(pAnim->Max.x);
	Header.MaxY = MathFunc::FromFP(pAnim->Max.y);
	Header.RunTime = MathFunc::FromFP(pAnim->RunTime);
	fwrite(&Header, sizeof(Header), 1, pFile);

	// save out each bone
	for (int i=0; i<Header.BoneC; i++)
	{
		BipedBone*	pBone = &pAnim->BoneA[i];
		BipedBinaryBone	BoneData;
		// export the bone headers
		strcpy(BoneData.Name, pBone->Name);
		strcpy(BoneData.DefaultImg, pBone->DefaultImg);
		BoneData.DefaultSortL = MathFunc::FromFP(pBone->DefaultSortL);
		BoneData.DefaultSortR = MathFunc::FromFP(pBone->DefaultSortR);
		BoneData.Length = MathFunc::FromFP(pBone->Length);
		fwrite(&BoneData, sizeof(BoneData), 1, pFile);
		// export the rest of the information

		// can we actually do a single memory-dump here?
		// fwrite(&pBone->FrameA[0], sizeof(BipedAnimFrame), pBone->FrameA.size(), pFile);
		BipedBinaryFrame	FrameInfo;
		for (int j=0; j<pBone->FrameC; j++)
		{
			FrameInfo.PosX = MathFunc::FromFP(pBone->FrameA[j].Pos.x);
			FrameInfo.PosY = MathFunc::FromFP(pBone->FrameA[j].Pos.y);
			FrameInfo.Rot = MathFunc::FromFP(pBone->FrameA[j].Rot);
			FrameInfo.Flags = pBone->FrameA[j].Flags;
			fwrite(&FrameInfo, sizeof(FrameInfo), 1, pFile);
		}
	}
	fclose(pFile);
}

void	BipedAnimationSet::SaveAll()
{
	for (unsigned int i=0; i<m_Animations.GetElementC(); i++)
	{
		SaveAnimationBin(m_Animations.GetElementByIndex(i));
	}
}

float	BipedAnimationSet::GetAnimationTime(const std::string& Name, float Default)
{
	BipedAnimation* pAnim = GetAnimation(Name);
	if (pAnim) return pAnim->RunTime;
	return Default;
}




BipedAnimation*	BipedAnimationSet::GetAnimation(const std::string& Name)
{
	return m_Animations.GetElement(Name);
}

////////////////////////

BipedAnimFrame*	BipedBone::GetRawFrame(int Frame, bool Wrap)
{
//	assert(FrameC!=0 && "Bones has no animation attached; how did that happen?");
	int FrameID = Frame;
	if (FrameID >= (int)FrameC)
	{
		if (!Wrap)
		{
			FrameID = (int)FrameC-1;
		} else
		{
			FrameID = Frame%(int)FrameC;
		}
	}
	return &FrameA[FrameID];
}

void	BipedBone::GetFrame(int Frame, math::Vec2& Pos, float& Rot, bool Wrap)
{
	assert(FrameC!=0 && "Bones has no animation attached; how did that happen?");
	int FrameID = Frame;
	if (FrameID >= (int)FrameC)
	{
		if (!Wrap)
		{
			FrameID = (int)FrameC-1;
		} else
		{
			FrameID = Frame%(int)FrameC;
		}
	}
	Pos = FrameA[FrameID].Pos;
	Rot = FrameA[FrameID].Rot;
}

void	BipedBone::GetBlendedFrame(float Frame, math::Vec2& Pos, float& Rot, bool Wrap)
{
	// break it into factors
	int		FrameA = (int)Frame;
	int		FrameB = FrameA+1;
	float	t = Frame - float(FrameA);

	// get the rotations
	math::Vec2		PosA, PosB;
	float			RotA, RotB;
	GetFrame(FrameA, PosA, RotA, Wrap);
	GetFrame(FrameB, PosB, RotB, Wrap);
	
	// blend
	Pos = PosA + ((PosB-PosA) * t);
	if (fabs(RotB-RotA) > 180.0f)
	{
		Rot = RotB + ((RotA-RotB) * (1.0f-t));
	} else
	{
		Rot = RotA + ((RotB-RotA) * t);
	}
}

void	BipedBone::GetFrameVec(int Frame, math::Vec2& Pos, math::Vec2& Dir, bool Wrap)
{
	float Rot;
	GetFrame(Frame, Pos, Rot, Wrap);
	math::Facing		Face(180, Rot);
	Dir = Face.GetForward();		// rotations can get in trouble here
}

void	BipedBone::GetFrameAngle(int Frame, math::Vec2& Pos, float& Angle, bool Wrap)
{
	GetFrame(Frame, Pos, Angle, Wrap);
}

void	BipedBone::GetBlendedFrameVec(float Frame, math::Vec2& Pos, math::Vec2& Dir, bool Wrap)
{
	// break it into factors
	int		FrameA = (int)Frame;
	int		FrameB = FrameA+1;
	float	t = Frame - float(FrameA);

	math::Vec2	PosA, PosB;
	math::Vec2	DirA, DirB;

	GetFrameVec(FrameA, PosA, DirA, Wrap);
	GetFrameVec(FrameB, PosB, DirB, Wrap);
	Pos = PosA + ((PosB-PosA) * t);
	Dir = DirA + ((DirB-DirA) * t);
	Dir.Normalize();
}